#!/usr/bin/evn python

import wsgiref.handlers
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template

from ptzmodels import *
from common.constants import *

import string

webapp.template.register_template_library('common.customtags')

"""
Displays a simple intro page.
"""
class PTZIntro(webapp.RequestHandler):
	def get(self):
		values = {
			#'error' : 'ptzintro'
		}
		self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', values))

"""
Allows users to build a mesh or set of correspondences.
"""
class PTZBuild(webapp.RequestHandler):
	def get(self):
		import urllib2
		import common.ptzCommon
		from google.appengine.api import users
		user = users.get_current_user()
		
		logInURL = users.create_login_url(self.request.url)
		logOutURL = users.create_logout_url(self.request.url)
		if not user:
		    username = None
		else:
		    username = user.nickname()
	
		sceneId = self.request.get('scene')
		sourceURL = self.request.get('source')
	
		# if the scene is in the title, get all the relevant info from that.
		if sceneId:
			ptzScene = PTZScene.get_by_key_name('ptzscene' + str(sceneId))
			if not ptzScene:
				self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', {'error': str(sceneId) + ' is not a PTZ scene.'}))
				return
			sourceURL = ptzScene.sourceURL
			codeBaseURL = ptzScene.codeBaseURL
			baseURL = ptzScene.baseURL
		elif sourceURL:
			ptzScene = PTZScene.all().filter('sourceURL =', sourceURL).get()
			if ptzScene:
				codeBaseURL = ptzScene.codeBaseURL
				baseURL = ptzScene.baseURL
			else:
				try:
					baseURL, codeBaseURL = common.ptzCommon.findCodeBaseURL(sourceURL)
				except (ValueError, urllib2.URLError, IOError):
					self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', {'error': 'Could not open url (' + sourceURL + ').'}))
					return
				except common.ptzCommon.CodeBaseNotFoundError:
					self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', {'error': 'No identifiable PTZ cameras in (' + sourceURL + ').'}))
					return
		else:
			self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', {'error': 'Provide a ?source= or a ?scene='}))
			return
		
		# now, we have the three URLs, as well as the scene, if it exists.
		values = {'ptzScene': ptzScene,
					'codeBaseURL': codeBaseURL,
					'baseURL' : baseURL,
					'sourceURL': sourceURL,
					'username' : username,
					'logInURL' :logInURL,
					'logOutURL' :logOutURL}
		self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'ptzBuild.html', values))
"""
Allows users to view a scene.
"""
class PTZViewScene(webapp.RequestHandler):
	def get(self):
		from google.appengine.api import users
		import hashlib
		sceneId = self.request.get('scene')
		scene = PTZScene.get_by_key_name('ptzscene' + sceneId)
		if not scene:
			self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', {'error': sceneId + ' is not a PTZ scene.'}))
			return
		
		corrs = PTZCorrespondence.all().filter('scene =', scene).fetch(CORRESPONDENCES_REQUIRED_FOR_PTZ_CALIBRATION)
		zooms = [c.zoom for c in corrs]
		minZoom = min(zooms)
		maxZoom = max(zooms)
		corrsLeft = CORRESPONDENCES_REQUIRED_FOR_PTZ_CALIBRATION - len(corrs)
		
		if corrsLeft == 0:
			corrsLeft = None
		
		calib = PTZCalibration.get_by_key_name('ptzcalib' + sceneId)
			
		user = users.get_current_user()
		logInURL = users.create_login_url("/ptz/viewScene?scene=" + str(sceneId))
		logOutURL = users.create_logout_url("/ptz/viewScene?scene=" + str(sceneId))
			
		votable = False
		username = None
		adminAccess = False
		if user:
			username = user.nickname()
			keyname = hashlib.md5(user.nickname() + '|ptz' + str(sceneId)).hexdigest()
			rateObj = Rating.get_by_key_name(keyname)
			if not rateObj:
				votable = True
			if username in ADMIN_EMAILS:
				adminAccess = True
			
		stars = [False] * 5
		for i in range(5):
			stars[i] = (scene.ratingAverage > i)
		
		values = {'scene': scene,
				  'corrsLeft': corrsLeft,
				  'calib' : calib,
				  'username' : username,
				  'logInURL' : logInURL,
				  'logOutURL' : logOutURL,
				  'admin' : adminAccess,
				  'votable' : votable,
				  'stars' : stars,
				  'maxZoom' : maxZoom,
				  'minZoom' : minZoom,
				  'username' : username,
				  'logInURL' :logInURL,
				  'logOutURL' :logOutURL}
		self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'ptzViewScene.html', values))
		
"""
Allows users to view a scene.
"""
class PTZCameraControl(webapp.RequestHandler):
	def get(self):
		from google.appengine.api import users
		import hashlib
		sceneId = self.request.get('scene')
		scene = PTZScene.get_by_key_name('ptzscene' + sceneId)
		if not scene:
			self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', {'error': sceneId + ' is not a PTZ scene.'}))
			return

		corrs = PTZCorrespondence.all().filter('scene =', scene).fetch(CORRESPONDENCES_REQUIRED_FOR_PTZ_CALIBRATION)
		corrsLeft = CORRESPONDENCES_REQUIRED_FOR_PTZ_CALIBRATION - len(corrs)

		if corrsLeft == 0:
			corrsLeft = None

		calib = PTZCalibration.get_by_key_name('ptzcalib' + sceneId)

		user = users.get_current_user()
		logInURL = users.create_login_url("/ptz/cameraControl?scene=" + str(sceneId))
		logOutURL = users.create_logout_url("/ptz/cameraControl?scene=" + str(sceneId))

		votable = False
		username = None
		adminAccess = False
		if user:
			username = user.nickname()
			keyname = hashlib.md5(user.nickname() + '|ptz' + str(sceneId)).hexdigest()
			rateObj = Rating.get_by_key_name(keyname)
			if not rateObj:
				votable = True
			if username in ADMIN_EMAILS:
				adminAccess = True

		stars = [False] * 5
		for i in range(5):
			stars[i] = (scene.ratingAverage > i)

		values = {'scene': scene,
					'corrsLeft': corrsLeft,
					'calib' : calib,
					'username' : username,
					'logInURL' : logInURL,
					'logOutURL' : logOutURL,
					'admin' : adminAccess,
					'votable' : votable,
					'stars' : stars}
		self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'ptzCameraControl.html', values))
		
"""
Insert a bunch of correspondences and meshes into the DB.
"""
class PTZInsert(webapp.RequestHandler):
	def post(self):
		import common.ptzCommon
		import common.commonDB
		import urllib2
		import string
		import time, datetime
		from google.appengine.api import memcache, users
		
		sourceURL = self.request.get('sourceURL')
		codeBaseURL = self.request.get('codeBaseURL')
		baseURL = self.request.get('baseURL')
		
		noPoints = int(self.request.get('noPoints'));
		pan = int(self.request.get('pan'));
		tilt = int(self.request.get('tilt'));
		zoom = int(self.request.get('zoom'));
		lats = string.split(self.request.get('lat'));
		lons = string.split(self.request.get('lon'));
		alts = string.split(self.request.get('alt'));
		us = string.split(self.request.get('u'));
		vs = string.split(self.request.get('v'));
		clockInTime = int(self.request.get('clockInTime'));
		clockOutTime = int(time.time())
		numberOfClicks = int(self.request.get('numberOfClicks'));
		
		assert noPoints == len(lats) == len(lons) == len(alts) == len(us) == len(vs)
		
		for i in range(noPoints):
			try:
				lats[i] = float(lats[i])
				lons[i] = float(lons[i])
				alts[i] = float(alts[i])
				us[i] = int(us[i])
				vs[i] = int(vs[i])
			except TypeError:
				self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', {'error': 'An element of PTZUVXYZ was non-numeric.'}))
				return
		
		# find out if this is an old scene or a new scene.
		ptzScene = None
		sceneId = self.request.get('scene')
		if sceneId:
			ptzScene = PTZScene.get_by_key_name('ptzscene' + sceneId)
		else:
			ptzScene = PTZScene.all().filter('sourceURL =', sourceURL).get()
			
		
		if not ptzScene:
			# make a new scene.
			panLeft, panRight, tiltUp, tiltDown, zoomIn, zoomOut, cameraType = common.ptzCommon.getPanTiltZoomRanges(baseURL);
		
			url = baseURL + '/-wvhttp-01-/GetOneShot';
		
			# we have to get the current webcam image.
			firstImage = None
			try:
				result = urllib2.urlopen(url)
				imageContents = result.read()
				firstImage = db.Blob(imageContents)
			except urllib2.URLError, e:
				# if we can't read it, route to the intro page.
				self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', {}))
				return
		
			width    = int(self.request.get('width'));
			height   = int(self.request.get('height'));
		
			totalPanDegrees = None
			totalTiltDegrees = None
			if cameraType == 'Canon VB-C50iR':
				totalPanDegrees = 340.0
				totalTiltDegrees = 120.0
				
			ptzScene = common.commonDB.addPTZScene(firstImage, url, width, height, sourceURL, codeBaseURL, baseURL, \
				panLeft, panRight, tiltUp, tiltDown, zoomIn, zoomOut, \
				cameraType, totalPanDegrees, totalTiltDegrees)
		
		if ptzScene and not ptzScene.cameraLatitude:
			ptzScene.cameraLatitude = lats[0];
			ptzScene.cameraLongitude = lons[0];
			ptzScene.put()
			
		
		for i in range(noPoints):
			ptzCorr = common.commonDB.addPTZCorrespondence(ptzScene, pan, tilt, zoom, lats[i], lons[i], alts[i], us[i], vs[i])


		# if there's a scene name, add it.
		sceneName = self.request.get('sceneName')
		if sceneName and not ptzScene.name:
			ptzScene.name = sceneName
			ptzScene.put()
			
		# add info about the user's observations.
		user = users.get_current_user()
		observation = UserObservation(
			clockIn = clockInTime,
			clockOut = clockOutTime,
			user = user,
			numberOfClicks = numberOfClicks,
			numberOfCorrespondences = noPoints,
			scene=ptzScene)
		observation.put()
		
		# go calibrate the scene through the client's computer.
		self.redirect("/ptz/calibrate?scene=" + str(ptzScene.index))

"""
Given a codebase, this returns the current pan, tilt, and zoom of the camera.
"""
class GetCurrPTZ(webapp.RequestHandler):
	def get(self):
		import common.ptzCommon
		baseURL = self.request.get('baseURL')
		if baseURL:
			try:
				pan,tilt,zoom = common.ptzCommon.getCurrentPanTiltZoom(baseURL);
				self.response.out.write(pan + ' ' + tilt + ' ' + zoom)
			except:
				self.response.out.write('-1 -1 -1')
		else:
			self.response.out.write('-1 -1 -1')

"""
Given a baseURL (such as http://grizcam.ito.umt.edu/), this returns the connection ID.
This connection must be created before attempting to move the camera.
"""
class ConnectToPTZ(webapp.RequestHandler):
	def get(self):
		import common.ptzCommon
		baseURL = self.request.get('baseURL')
		if baseURL:
			try:
			    connectionID = common.ptzCommon.connectToCamera(baseURL)
			    self.response.out.write(str(connectionID))
			except:
				self.response.out.write('-1')
		else:
			self.response.out.write('-1')
			
"""
Once a connection ID has been created, call this function to place yourself in the control queue.
"""
class GetPTZControl(webapp.RequestHandler):
	def get(self):
		import common.ptzCommon
		baseURL = self.request.get('baseURL')
		connectionID = self.request.get('connectionID')
		success = False
		if baseURL and connectionID:
			try:
				success = common.ptzCommon.getCameraControl(baseURL, connectionID)
			except:
				pass

		if success:
			self.response.out.write('1')
		else:
			self.response.out.write('-1')
			
"""
Once a connection ID has been created and you're in the queue, call this function to move the camera.
This will print out one of four things:
	'success' if the camera moved,
	'unknown connection' if the connection is out-of-date or has not been made.
	'not your turn' if it is not your turn.
	'failure' if there is some other failure.
"""
class MovePTZCamera(webapp.RequestHandler):
	def get(self):
		import common.ptzCommon
		baseURL = self.request.get('baseURL')
		connectionID = self.request.get('connectionID')
		pan = self.request.get('pan')
		tilt = self.request.get('tilt')
		zoom = self.request.get('zoom')
		message = 'failure'
		if baseURL and connectionID:
			try:
				message = common.ptzCommon.operateCamera(baseURL, connectionID, pan, tilt, zoom)
			except:
				pass
		self.response.out.write(message)

"""
View a page of scenes.
"""
class PTZViewAll(webapp.RequestHandler):
	def get(self):
		from google.appengine.api import users
		user = users.get_current_user()
		
		logInURL = users.create_login_url(self.request.url)
		logOutURL = users.create_logout_url(self.request.url)
		if not user:
		    username = None
		else:
		    username = user.nickname()
		page = self.request.get("page")
		if not page:
			page = 1
		else:
			page = int(page)
			
		scenes = PTZScene.all().order("-ratingAverage").order("index").fetch(RESULTS_PER_PAGE, (page-1)*RESULTS_PER_PAGE)
		
		gc = GlobalCounter.get_by_key_name('gcptz')
		if not gc:
			values = {'message': 'Apparently our growing library of PTZ scenes isn\'t so big.  Couldn\'t find any scenes.'}
			self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'middle.html', values))
			return
				
		lastPage = gc.numScenes / RESULTS_PER_PAGE
		if (gc.numScenes % RESULTS_PER_PAGE) > 0:
			lastPage = lastPage + 1
		
		pagesMarked = [page]
		firstGroup = []
		lastGroup = []
		prev = []
		next = []
		
		for scene in scenes:
			stars = [False] * 5
			for i in range(5):
				stars[i] = (scene.ratingAverage > i)
			scene.stars = stars
		
		for i in range(1,lastPage+1):
			if i < page and i <= FIRST_PAGES and i not in pagesMarked:
				firstGroup.append(i)
				pagesMarked.append(i)
			if i > page and lastPage - i < LAST_PAGES and i not in pagesMarked:
				lastGroup.append(i)
				pagesMarked.append(i)
			if i < page and page - i <= PREV_PAGES and i not in pagesMarked:
				prev.append(i)
				pagesMarked.append(i)
			if i > page and i - page <= NEXT_PAGES and i not in pagesMarked:
				next.append(i)
				pagesMarked.append(i)
		
		prevEllipsis = False
		nextEllipsis = False
		for i in range(1,lastPage+1):
			if i < page and i not in pagesMarked:
				prevEllipsis = True
			if i > page and i not in pagesMarked:
				nextEllipsis = True
			if prevEllipsis and nextEllipsis:
				break
			

		values = {
			'scenes' : scenes,
			'firstGroup'   : firstGroup,
			'prev'   : prev,
			'thisPage' : page,
			'next' : next,
			'lastGroup' : lastGroup,
			'prevEllipsis' : prevEllipsis,
			'nextEllipsis' : nextEllipsis,
			'username' : username,
			'logInURL' :logInURL,
			'logOutURL' :logOutURL
		}
		self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'ptzViewAll.html', values))
	


class PTZCalibrate(webapp.RequestHandler):
	def get(self):
		from google.appengine.api import users
		user = users.get_current_user()
		
		logInURL = users.create_login_url(self.request.url)
		logOutURL = users.create_logout_url(self.request.url)
		if not user:
		    username = None
		else:
		    username = user.nickname()
		sceneId = self.request.get('scene')
		scene = PTZScene.get_by_key_name('ptzscene' + sceneId)
		if not scene:
			values = {'error': sceneId + ' is not a PTZ scene.'}
			self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', values))
			return
			
		corrs = PTZCorrespondence.all().filter('scene =', scene).fetch(100);
		if len(corrs) == 0:
			values = {'error': sceneId + ' does not have any correspondences.'}
			self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', values))
			return
		if len(corrs) < CORRESPONDENCES_REQUIRED_FOR_PTZ_CALIBRATION:
			self.redirect("/ptz/viewScene?scene=" + str(scene.index))
			return
		
		originLatitude = 0.0;
		originLongitude = 0.0;
		originAltitude = 0.0;
		for c in corrs:
			originLatitude = originLatitude + c.latitude;
			originLongitude = originLongitude + c.longitude;
			originAltitude = originAltitude + c.altitude;

		originLatitude = originLatitude / float(len(corrs))
		originLongitude = originLongitude / float(len(corrs))
		originAltitude = originAltitude / float(len(corrs))

		admin = False
		if user and user.nickname() in ADMIN_EMAILS:
			admin = True

		values = {
			'ptzScene' : scene,
			'corrs'   : corrs,
			'originLatitude' : originLatitude,
			'originLongitude' : originLongitude,
			'originAltitude' : originAltitude,
			'admin' : admin,
			'username' : username,
			'logInURL' :logInURL,
			'logOutURL' :logOutURL
		}
		self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'ptzCalibrate.html', values))
		
		
# I should probably find a way to not duplicate code, but eh.....
class PTZCorruptCalibration(webapp.RequestHandler):
	def get(self):
		from google.appengine.api import users
		sceneId = self.request.get('scene')
		scene = PTZScene.get_by_key_name('ptzscene' + sceneId)
		if not scene:
			values = {'error': sceneId + ' is not a PTZ scene.'}
			self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', values))
			return

		corrs = PTZCorrespondence.all().filter('scene =', scene).fetch(100);
		if len(corrs) == 0:
			values = {'error': sceneId + ' does not have any correspondences.'}
			self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'home.html', values))
			return
		if len(corrs) < CORRESPONDENCES_REQUIRED_FOR_PTZ_CALIBRATION:
			self.redirect("/ptz/viewScene?scene=" + str(scene.index))
			return

		originLatitude = 0.0;
		originLongitude = 0.0;
		originAltitude = 0.0;
		for c in corrs:
			originLatitude = originLatitude + c.latitude;
			originLongitude = originLongitude + c.longitude;
			originAltitude = originAltitude + c.altitude;

		originLatitude = originLatitude / float(len(corrs))
		originLongitude = originLongitude / float(len(corrs))
		originAltitude = originAltitude / float(len(corrs))

		admin = False
		user = users.get_current_user()
		if user and user.nickname() in ADMIN_EMAILS:
			admin = True

		values = {
			'ptzScene' : scene,
			'corrs'   : corrs,
			'originLatitude' : originLatitude,
			'originLongitude' : originLongitude,
			'originAltitude' : originAltitude,
			'admin' : admin,
			'username' : username,
			'logInURL' :logInURL,
			'logOutURL' :logOutURL
		}
		self.response.out.write(template.render(TEMPLATE_DIRECTORY + 'ptzCorruptCalibration.html', values))
		
class PTZSetCalibration(webapp.RequestHandler):
	def post(self):
		import common
		from common.commonDB import addOrModifyPTZCalibration;
		sceneId = self.request.get('scene')
		scene = PTZScene.get_by_key_name('ptzscene' + sceneId)
		if not scene:
			self.response.out.write('-1')
			return
		try:
			originLat = float(self.request.get('originLat'))
			originLon = float(self.request.get('originLon'))
			originAlt = float(self.request.get('originAlt'))
			tx = float(self.request.get('tx'))
			ty = float(self.request.get('ty'))
			tz = float(self.request.get('tz'))
			cameraLat = float(self.request.get('cameraLat'))
			cameraLon = float(self.request.get('cameraLon'))
			cameraAlt = float(self.request.get('cameraAlt'))
			panOffsetRad = float(self.request.get('panOffsetRad'))
			tiltOffsetRad = float(self.request.get('tiltOffsetRad'))
			rollOffsetRad = float(self.request.get('rollOffsetRad'))
			focalLengthF1 = float(self.request.get('focalLengthF1'))
			focalLengthF2 = float(self.request.get('focalLengthF2'))
			focalLengthF3 = float(self.request.get('focalLengthF3'))
			
			totalPanDegrees = float(self.request.get('totalPanDegrees'))
			totalTiltDegrees = float(self.request.get('totalTiltDegrees'))
		except TypeError:
			self.response.out.write('-1')
			return
			
		
		calib = common.commonDB.addOrModifyPTZCalibration(scene, originLat, originLon, originAlt, tx, ty, tz, panOffsetRad, \
											  tiltOffsetRad, rollOffsetRad, focalLengthF1, focalLengthF2, focalLengthF3);
		
		scene.cameraLatitude = cameraLat
		scene.cameraLongitude = cameraLon
		scene.cameraAltitude = cameraAlt
		scene.totalPanDegrees = totalPanDegrees
		scene.totalTiltDegrees = totalTiltDegrees
		scene.calibrated = True
		scene.put()
		
		self.response.out.write('1')

def main():
	app = webapp.WSGIApplication([(r'/ptz/', PTZIntro),
								  (r'/ptz/build', PTZBuild),
								  (r'/ptz/insert', PTZInsert),
								  (r'/ptz/viewScene', PTZViewScene),
								  (r'/ptz/cameraControl', PTZCameraControl),
								  (r'/ptz/connectToPTZ', ConnectToPTZ),
								  (r'/ptz/getPTZControl', GetPTZControl),
								  (r'/ptz/movePTZCamera', MovePTZCamera),
								  (r'/ptz/getCurrPtz', GetCurrPTZ),
								  (r'/ptz/viewAll', PTZViewAll),
								  (r'/ptz/setCalibration', PTZSetCalibration),
								  (r'/ptz/calibrate', PTZCalibrate),
								  (r'/ptz/corruptCalibration', PTZCorruptCalibration)],
								 debug=True)
	wsgiref.handlers.CGIHandler().run(app)
	
if __name__ == '__main__':
	main()